package utilities.module;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Properties;

import utilities.control.iface.UserControlListener;
import utilities.display.iface.Drawable;

/** <code>GameModule</code><br>
 * <br>
 * Apparently subject to change at a whim. Will have a more concrete javadoc later.
 * <br>
 * 
 * @author CoconutTank
 *
 */
public abstract class Module implements 
								 Serializable, 
								 UserControlListener,
								 Drawable
{
	/**
	 * 
	 */
	private static final long serialVersionUID = 6400882216892240982L;
	
	/*******************************************************************
	 * 
	 * VARIABLES
	 * 
	 *******************************************************************/
	
	//protected int x;
	//protected int y;
	
	private Rectangle displayArea;
	
	protected Module parent;
	protected ArrayList<Module> children;
	
	/*******************************************************************
	 * 
	 * CONSTRUCTORS
	 * 
	 *******************************************************************/
	
	/**
	 * 
	 */
	protected Module()
	{
		this.parent = null;
		this.children = new ArrayList<Module>();
		displayArea = new Rectangle(0, 0, 0, 0);
	}
	
	/**
	 * 
	 * @param x TODO
	 * @param y TODO
	 * @param width TODO
	 * @param height TODO
	 */
	protected Module(int x, int y, int width, int height)
	{
		this.parent = null;
		this.children = new ArrayList<Module>();
		displayArea = new Rectangle(x, y, width, height);
	}
	
	/**
	 * 
	 * @param x TODO
	 * @param y TODO
	 * @param width TODO
	 * @param height TODO
	 * @param parent
	 * @param children
	 */
	protected Module(int x, int y, int width, int height, Module parent, Module... children)
	{
		this.parent = parent;
		this.children = new ArrayList<Module>();		
		displayArea = new Rectangle(x, y, width, height);
		
		if(children != null && children.length > 0)
		{
			for(Module child : children)
			{
				if(child != null) this.children.add(child);
			}
		}
	}
	
	/*******************************************************************
	 * 
	 * ABSTRACT METHODS
	 * 
	 *******************************************************************/
	
	/**
	 * 
	 */
	public abstract void update(Properties globals);
	
	/*******************************************************************
	 * 
	 * INHERITED METHODS
	 * 
	 *******************************************************************/

	public void keyTyped(KeyEvent e) {}
	
	public void keyPressed(KeyEvent e) {}

	public void keyReleased(KeyEvent e) {}
	
	public void mouseClicked(MouseEvent e) {}

	public void mousePressed(MouseEvent e) {}

	public void mouseReleased(MouseEvent e) {}

	public void mouseEntered(MouseEvent e) {}

	public void mouseExited(MouseEvent e) {}

	public void mouseDragged(MouseEvent e) {}

	public void mouseMoved(MouseEvent e) {}

	public void mouseWheelMoved(MouseWheelEvent e) {}
	
	/*******************************************************************
	 * 
	 * METHODS
	 * 
	 *******************************************************************/
	
	/**
	 * Returns the x position of this GameModule's origin in the content area.
	 * 
	 * @return
	 */
	public final int getX() { return displayArea.x; }
	
	/**
	 * Returns the y position of this GameModule's origin in the content area.
	 * 
	 * @return
	 */
	public final int getY() { return displayArea.y; }
	
	/**
	 * 
	 * @return
	 */
	public final int getWidth() { return displayArea.width; }
	
	/**
	 * 
	 * @return
	 */
	public final int getHeight() { return displayArea.height; }
	
	/**
	 * Returns the difference between the x positions of this GameModule's origin and its immediate parent's origin, if any.<br>
	 * 
	 * @return
	 */
	public final int getRelativeX()
	{
		return displayArea.x - (parent != null ? parent.getX() : 0);
	}
	
	/**
	 * Returns the vertical distance between the x positions of this GameModule's origin and its immediate parent's origin, if any.<br>
	 * 
	 * @return
	 */
	public final int getRelativeY()
	{
		return displayArea.y + (parent != null ? parent.getY() : 0);
	}
	
	/**
	 * 
	 * @param p
	 * @return
	 */
	public final boolean contains(Point p)
	{
		return displayArea.contains(p);
	}
	
	/**
	 * 
	 * @param x
	 * @param y
	 * @return
	 */
	public final boolean contains(int x, int y)
	{
		return displayArea.contains(x, y);
	}
	
	/**
	 * 
	 * @param dx
	 * @param dy
	 */
	public final void translate(int dx, int dy)
	{
		displayArea.x += dx;
		displayArea.y += dy;
		
		for(Module child : children)
		{
			if(child != null) child.translate(dx, dy);
		}
	}
	
	/**
	 * 
	 * @param width
	 */
	public final void setWidth(int width)
	{
		displayArea.width = width;
	}
	
	/**
	 * 
	 * @param height
	 */
	public final void setHeight(int height)
	{
		displayArea.height = height;
	}
	
	/**
	 * 
	 * @return
	 */
	public Module getParent()
	{
		return parent;
	}
	
	/**
	 * 
	 * @param parent
	 */
	public void setParent(Module parent)
	{
		if(parent != null && this.parent == null) this.parent = parent;
	}
	
	/**
	 * 
	 * @param child
	 */
	public boolean removeChild(Module child)
	{
		if(child != null && children != null && children.size() > 0)
		{
			return children.remove(child);
		}
		
		return false;
	}
	
	/**
	 * 
	 * @param index
	 * @return
	 */
	public Module removeChild(int index)
	{
		Module child = null;
		
		if(children != null && index >= 0 && index < children.size())
		{
			child = children.remove(index);
		}
		
		return child;
	}
	
	/**
	 * 
	 * @param child
	 */
	public void addChild(Module child)
	{
		if(children != null && child != null)
		{
			children.add(child);
		}
	}
	
	/**
	 * 
	 * @param child
	 * @param index
	 */
	public void addChild(Module child, int index)
	{
		if(children != null && child != null)
		{
			if(index < 0)
				children.add(0, child);
			else if(index >= children.size())
				children.add(child);
			else
				children.add(index, child);
		}
	}
	
	/**
	 * Returns the nth module found within the content area that contains the position (x, y).<br>
	 * The user can specify whether to search for the modules in order or in reverse.<br>
	 * If no modules are found, null is returned.<br>
	 * 
	 * @param x
	 * @param y
	 * @param nthModule
	 * @param inOrder
	 * @return
	 */
	public Module getNthChildrenAt(int x, int y, int nthModule, boolean inOrder)
	{
		if(children != null || nthModule < 1)
		{
			int nCount = 0;
			
			int idx = 0;
			int inc = 1;
			int limit = children.size();
			
			if(!inOrder)
			{
				idx = children.size() - 1;
				inc = -1;
				limit = -1;
			}
			
			for( ; idx != limit; idx += inc)
			{
				Module mod = children.get(idx);				
				if(mod != null && mod.contains(x, y) && (++nCount == nthModule)) return mod;
			}
		}
		
		return null;
	}
	
	/**
	 * Returns all modules found within the content area that contain the position (x, y).<br>
	 * The user can specify whether to search for the modules in order or in reverse. Doing so
	 * affects how the resulting modules are listed.<br>
	 * If no modules are found, null is returned.<br>
	 * 
	 * @param x
	 * @param y
	 * @param inOrder
	 * @return
	 */
	public ArrayList<Module> getAllChildrenAt(int x, int y, boolean inOrder)
	{
		ArrayList<Module> list = new ArrayList<Module>();
		
		if(children != null)
		{
			int idx = 0;
			int inc = 1;
			int limit = children.size();
			
			if(!inOrder)
			{
				idx = children.size() - 1;
				inc = -1;
				limit = -1;
			}
			
			for( ; idx != limit; idx += inc)
			{
				Module mod = children.get(idx);				
				if(mod != null && mod.contains(x, y)) list.add(mod);
			}
		}
		
		return (list.size() > 0 ? list : null);
	}
}